1
1
.
.
4
4
.
.
5
5
S
S
e
e
c
c
u
u
r
r
i
i
t
t
y
y
E
E
x
x
p
p
r
r
e
e
s
s
s
s
i
i
o
o
n
n
s
s
-
-
@
@
P
P
r
r
e
e
A
A
u
u
t
t
h
h
o
o
r
r
i
i
z
z
e
e
-
-
C
C
u
u
s
s
t
t
o
o
m
m
M
M
e
e
t
t
h
h
o
o
d
d
s
s
-
-
B
B
o
o
o
o
k
k
s
s
I
I
n
n
f
f
o
o
[
[
G
G
]
]
This tutorial shows how to use Custom Method to Authorize access to Endpoint.
We will create Custom Class AuthenticationService.java with Method authenticate() that returns
● true if Authenticated User owns the Book it wants to read
● false if Authenticated User doesn't own the Book it wants to read
User will call http://localhost:8080/ReadBook/1 where 1 represents bookId.
We will load DB with few Books (as shown below).
MyController.java
@PreAuthorize("hasAuthority('book.read') AND @authenticationService.authenticate(authentication)")
Application Schema [Results]
DB Table: Book (preloaded data)
ID
TITLE
USERNAME
1
Book about dogs
john
2
Book about cats
bill
Spring Boot Starters
GROUP
DEPENDENCY
DESCRIPTION
Web
Spring Web
Enables @Controller, @RequestMapping and Tomcat Server
Security
Spring Security
Enables Spring Security
SQL
Spring Data JPA
Enables @Entity and @Id
SQL
H2 Database
Enables in-memory H2 Database
AuthenticationService
http://localhost:8080/ReadBook
/1
readBook()
AccountService
MyController
Browser
Tomcat
Book
BookRepository
P
P
r
r
o
o
c
c
e
e
d
d
u
u
r
r
e
e
Create Project: springboot_security_authorization_custom_books (add Spring Boot Starters from the table)
Edit File: application.properties (specify Username, Password, Role)
Create Package: entities (inside package main)
– Create Class: PersonEntity.java (inside package entities)
Create Package: repositories (inside package main)
– Create Interface: PersonRepository.java (inside package repositories)
Create Package: config (inside package main)
– Create Class: BookLoader.java (inside package config)
– Create Class: SecurityConfig.java (inside package config)
Create Package: services (inside package main)
– Create Class: AccountService.java (inside package services)
– Create Class: AuthenticationService.java (inside package services)
Create Package: controllers (inside package main)
– Create Class: MyController.java (inside package controllers)
application.properties
# SECURITY
spring.security.user.name = john
spring.security.user.password = mypassword
spring.security.user.profile = USER
# CRUD AUTHORITIES
profile.user = book.read
profile.admin = book.create, book.read, book.update, book.delete
# H2 DATABASE
spring.h2.console.enabled = true
spring.datasource.url = jdbc:h2:mem:testdb
Book.java
package com.ivoronline.springboot_security_authorization_custom_books.entities;
import org.springframework.stereotype.Component;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@Component
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer id;
public String userName;
public String title;
}
BookRepository.java
package com.ivoronline.springboot_security_authorization_custom_books.repositories;
import com.ivoronline.springboot_security_authorization_custom_books.entities.Book;
import org.springframework.data.repository.CrudRepository;
public interface BookRepository extends CrudRepository<Book, Integer> { }
BookLoader.java
package com.ivoronline.springboot_security_authorization_custom_books.config;
import com.ivoronline.springboot_security_authorization_custom_books.entities.Book;
import com.ivoronline.springboot_security_authorization_custom_books.repositories.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class BookLoader implements CommandLineRunner {
@Autowired
private BookRepository bookRepository;
@Override
@Transactional
public void run(String... args) throws Exception {
//BBOK1
Book book1 = new Book();
book1.title = "Book about dogs";
book1.userName = "john";
//BBOK2
Book book2 = new Book();
book2.title = "Book about cats";
book2.userName = "bill";
//STORE ACCOUNT INTO DB
bookRepository.save(book1);
bookRepository.save(book2);
}
}
MyController.java
package com.ivoronline.springboot_security_authorization_custom_books.controllers;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class MyController {
@ResponseBody
@RequestMapping("/ReadBook/{BookId}")
@PreAuthorize("hasAuthority('book.read') AND @authenticationService.authenticate(authentication, #bookId)")
public String readBook(@PathVariable("BookId") String bookId) {
return "USER can read his Book";
}
}
AuthenticationService.java
package com.ivoronline.springboot_security_authorization_custom_books.services;
import com.ivoronline.springboot_security_authorization_custom_books.entities.Book;
import com.ivoronline.springboot_security_authorization_custom_books.repositories.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class AuthenticationService {
@Autowired
BookRepository bookRepository;
@Transactional
public boolean authenticate(Authentication authentication, Integer bookId) {
//GET BOOK
Book book = bookRepository.findById(bookId).get();
//GET BOOK USERNAME
String bookUserName = book.userName;
//GET AUTHENTICATED USERNAME
UserDetails user = (UserDetails) authentication.getPrincipal();
String authenticatedUserName = user.getUsername();
//CHECK OWNERSHIP
if (bookUserName.equals(authenticatedUserName)) { return true; }
else { return false; }
}
}
SecurityConfig.java
package com.ivoronline.springboot_security_authorization_custom_books.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
//H2 CONSOLE
httpSecurity.authorizeRequests(authorize -> { authorize.antMatchers("/h2-console/**").permitAll(); });
httpSecurity.headers().frameOptions().sameOrigin();
httpSecurity.csrf().disable();
//EVERYTHING ELSE IS LOCKED
httpSecurity.formLogin();
}
}
AccountService.java
package com.ivoronline.springboot_security_authorization_custom_books.services;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
public class AccountService implements UserDetailsService {
//LOAD PROPERTIES (from application.properties file)
@Value("${spring.security.user.name}") private String userName;
@Value("${spring.security.user.password}") private String userPassword;
@Value("${spring.security.user.profile}") private String userProfile;
@Value("${profile.user}") private String profileUser;
@Value("${profile.admin}") private String profileAdmin;
@Override
public UserDetails loadUserByUsername(String enteredUserName) throws UsernameNotFoundException {
//CHECK USERNAME
if(!enteredUserName.equals(userName)) { throw new UsernameNotFoundException("User not found"); }
//GET AUTHORITIES FOR GIVEN USER PROFILE
String userAuthorities = "";
if(userProfile.equals("USER") ) { userAuthorities = profileUser; }
if(userProfile.equals("ADMIN")) { userAuthorities = profileAdmin; }
//GET AUTHORITIES FROM STRING PROPERTY
String[] authoritiesArray = userAuthorities.split(", ");
List<String> authoritiesList = Arrays.asList(authoritiesArray);
//CREATE AUTHORITIES (FOR USER OBJECT)
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String authority : authoritiesList) {
authorities.add(new SimpleGrantedAuthority(authority.trim()));
}
//CREATE USER
User user = new User(userName, userPassword, true, true, true, true, authorities);
//RETURN USER
return user;
}
}
R
R
e
e
s
s
u
u
l
l
t
t
s
s
http://localhost:8080/ReadBook/1
@PreAuthorize("hasAuthority('book.read') AND @authenticationService.authenticate(authentication)")
http://localhost:8080/h2-console
Application Structure
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>